package com.google.code.krillion.firehose.server;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.ResourceBundle;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.google.code.krillion.firehose.data.Friend;
import com.google.code.krillion.firehose.data.PMF;
import com.google.code.krillion.firehose.data.Reply;

import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.User;
import twitter4j.http.HttpClient;
import twitter4j.http.HttpResponse;

public class MessageWorkerServlet implements Servlet {
    private static Logger log = Logger.getLogger(MessageWorkerServlet.class.getName());
	private static ServletConfig config;
	private static String username;
	private static String password;
	private static Twitter twitter;
	private static String apiCall;
	
	private static Pattern UPC_PATTERN = Pattern.compile(
			"^\\s*(U|u|Upc|upc)\\s+([0-9]{12})\\s*$");
	
	private static Pattern ZIP_PATTERN = Pattern.compile(
			"^\\s*(Z|z|Zip|zip)\\s+([0-9]{5})\\s*$");
	
	private static Pattern UPC_ZIP_PATTERN = Pattern.compile(
			"^\\s*(U|u|Upc|upc)\\s+([0-9]{12})\\s+([0-9]{5})\\s*$");
	
	private static Pattern NO_PARAM_PATTERN = Pattern.compile(
			"^\\s*(U|u|Upc|upc|Z|z|Zip|zip)\\s*$");
	
	private static final String MSG_NO_UPC_AND_ZIP =
		"please specify UPC and zipcode values before requesting best local price";
	
	private static final String MSG_NO_UPC =
		"please specify UPC value before requesting best local price";
	
	private static final String MSG_NO_ZIP =
		"please specify zipcode value before requesting best local price";
	
	private static final String MSG_NO_UPC_MATCH =
		"Sorry, the Krillion service does not handle this UPC code yet, please try again later";
	
	
	@Override
	public void destroy() {
		config = null;
		username = null;
		password = null;
		apiCall = null;
	}

	@Override
	public ServletConfig getServletConfig() {
		return config;
	}

	@Override
	public String getServletInfo() {
		return null;
	}

	@Override
	public void init(ServletConfig sc) throws ServletException {
		config = sc;
		ResourceBundle bundle = ResourceBundle.getBundle("com.google.code.krillion.firehose.server.krillion");
		username = (String) bundle.getObject("kritter.username");
		password = (String) bundle.getObject("kritter.password");
		if (username != null && password != null) {
			TwitterFactory factory = new TwitterFactory();
		    twitter = factory.getInstance(username,password);
			try {
				twitter.verifyCredentials();
			} catch (TwitterException e) {
				log.severe("TwitterException verifying user credentials: " + e.getMessage());
			}
		}
		apiCall = (String) bundle.getObject("krillion.api.call.upc");
	}

	@Override
	public void service(ServletRequest req, ServletResponse resp)
	throws ServletException, IOException {
		String friendName = req.getParameter("friend");
		String message = req.getParameter("message");
		log.info("friend:" + friendName + ", message:" + message);
		boolean stop = false;
		Matcher match = UPC_ZIP_PATTERN.matcher(message);
		if (match.find()) {
			processMessage(friendName, message, match.group(2), match.group(3));
			stop = true;
		}
		if (!stop) {
			match = UPC_PATTERN.matcher(message);
			if (match.find()) {
				processMessage(friendName, message, match.group(2), null);
				stop = true;
			}
		}
		if (!stop) {
			match = ZIP_PATTERN.matcher(message);
			if (match.find()) {
				processMessage(friendName, message, null, match.group(2));
				stop = true;
			}
		}
		if (!stop) {
			match = NO_PARAM_PATTERN.matcher(message);
			if (match.find()) {
				processMessage(friendName, message, null, null);
				stop = true;
			}
		}
	}

	private void processMessage(String friendName, String question, String upc, String zip) {
		try {
			boolean isValidCommand = false;

			// Persistence Part 1: this will involve reading and writing.
			// When upc and/or zip are not specified, we attempt to read
			// one or both from the associated Friend record. We also
			// write back to the friend record any new incoming upc
			// and/or zip values from the question.
			PersistenceManager pm = PMF.get().getPersistenceManager();
			Query query = pm.newQuery(Friend.class);
			query.setFilter("friendName == name");
			query.declareParameters("String name");
			query.setUnique(true);
			try {
				Friend friend = (Friend) query.execute(friendName);
				if (friend == null) {
					User follower = twitter.showUser(friendName);
					friend = new Friend(String.valueOf(follower.getId()), friendName);
					if (upc == null && zip == null) {
						twitter.sendDirectMessage(friendName, MSG_NO_UPC_AND_ZIP);
					} else if (upc == null) {
						twitter.sendDirectMessage(friendName, MSG_NO_UPC);
					} else if (zip == null) {
						twitter.sendDirectMessage(friendName, MSG_NO_ZIP);
					} else {
						friend.setUpc(upc);
						friend.setZip(zip);
						isValidCommand = true;
					}
					pm.makePersistent(friend);
				} else {
					if (  upc == null && Friend.UNDEFINED_VALUE.equals(friend.getUpc())
					   && zip == null && Friend.UNDEFINED_VALUE.equals(friend.getZip())) {
						twitter.sendDirectMessage(friendName, MSG_NO_UPC_AND_ZIP);
					} else if (upc == null && Friend.UNDEFINED_VALUE.equals(friend.getUpc())) {
						twitter.sendDirectMessage(friendName, MSG_NO_UPC);
					} else if (zip == null && Friend.UNDEFINED_VALUE.equals(friend.getZip())) {
						twitter.sendDirectMessage(friendName, MSG_NO_ZIP);
					} else {
						if (upc != null) friend.setUpc(upc);
						else upc = friend.getUpc();
						if (zip != null) friend.setZip(zip);
						else zip = friend.getZip();
						pm.makePersistent(friend);
						isValidCommand = true;
					}
				}
				if (isValidCommand) {
					Object[] args = {upc, zip};
					final String url = MessageFormat.format(apiCall, args);
					HttpClient client = new HttpClient();
					HttpResponse response = client.get(url);
//					final String responseStr = response.asString();
//					log.info("responseAsString: " + responseStr);
					Document dom = response.asDocument();
					NodeList productList = dom.getElementsByTagName("product");
					NodeList titleList   = dom.getElementsByTagName("title");
					NodeList lowestPriceList   = dom.getElementsByTagName("lowestPrice");
					if (  productList.getLength() > 0
					   && titleList.getLength()   > 0
					   && lowestPriceList.getLength()   > 0) {
						final Node titleNode = titleList.item(0);
						final Node lowestPriceNode = lowestPriceList.item(0);
						final Node linkNode  = productList.item(0).getAttributes().getNamedItem("url");
						
						if (titleNode != null && lowestPriceNode != null  && linkNode != null) {
							final String titleText = titleNode.getTextContent();
							final String priceText = lowestPriceNode.getTextContent();
							final String linkText  = linkNode.getTextContent();
							StringBuilder buf = new StringBuilder(140);
							buf.append("best local price for ")
							.append(titleText)
							.append(" near ")
							.append(zip)
							.append(" is $")
							.append(priceText)
							.append(" ")
							.append(linkText);
							final String answer = buf.toString();
							log.info("friend:" + friendName + ", answer:" + answer);
							// Send direct message back to friend that asked the question
							twitter.sendDirectMessage(friendName, answer);
							// Also post answer as a status update to Kritter's public timeline
							twitter.updateStatus(answer);

							// Persistence Part 2: record the Reply in our datastore.
							Reply reply = new Reply(friendName, question, answer, upc, zip);
							pm.makePersistent(reply);
						} else {
							twitter.sendDirectMessage(friendName, MSG_NO_UPC_MATCH);
							log.warning("Extracting title/price/link from product Node failed");
						}
					} else {
						twitter.sendDirectMessage(friendName, MSG_NO_UPC_MATCH);
						log.warning("XML response dom lookup failed for product, upc:" + upc + ", zip:" + zip);
					}
				}
			} finally {
				query.closeAll();
				pm.close();
			}

		} catch (TwitterException e) {
			log.severe("TwitterException processing friend's message: " + e.getMessage());
		}
	}

}
